package cn.wlinker.driver.bacnet.utils;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.StaticLog;
import cn.wlinker.driver.bacnet.domain.BacnetLocalDeviceBean;
import cn.wlinker.driver.common.constant.Constant;
import cn.wlinker.driver.common.exception.DriverInitException;
import com.serotonin.bacnet4j.LocalDevice;
import com.serotonin.bacnet4j.npdu.ip.IpNetwork;
import com.serotonin.bacnet4j.npdu.ip.IpNetworkBuilder;
import com.serotonin.bacnet4j.transport.DefaultTransport;

import java.net.InetSocketAddress;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 虚拟bacnet设备
 *
 * @author gxsjx
 * @version 1.0
 * @date 2022/9/18
 * Copyright © wlinker.cn
 */
public class BacnetLocalDeviceHelper {

    /**
     * 本地设备id最小值
     */
    public static final int BASE_MIN_ID = 960000;
    /**
     * 增量id最大值
     */
    public static final int LOCAL_DEVICE_ID_ADD_MAX = 9999;
    /**
     * 5分钟超时
     */
    private static final long DISCOVERY_SHUTDOWN_TIMEOUT_MS = 5 * 60 * 1000;

    public static LocalDevice getLocalDevice(BacnetLocalDeviceBean bean) throws Exception {
        LocalDevice localDevice = LocalDeviceCacheUtils.get(bean);
        if (localDevice != null && localDevice.isInitialized()) {
            //startRemoteDeviceDiscovery(localDevice);
            return localDevice;
        }
        synchronized (BacnetLocalDeviceHelper.class) {
            localDevice = LocalDeviceCacheUtils.get(bean);
            if (localDevice == null || !localDevice.isInitialized()) {
                localDevice = initLocalDevice(bean);
            }
        }
        return localDevice;
    }

    /**
     * 初始化本地设备
     * @param bacnetLocalDeviceBean
     * @return
     * @throws Exception
     */
    private static synchronized LocalDevice initLocalDevice(BacnetLocalDeviceBean bacnetLocalDeviceBean) throws Exception {
        LocalDevice localDevice = LocalDeviceCacheUtils.get(bacnetLocalDeviceBean);
        if (ObjectUtil.isNotNull(localDevice) && !localDevice.isInitialized()) {
            localDevice.terminate();
        }
        try {
            String localBindAddress = bacnetLocalDeviceBean.getLocalBindAddress();
            IpNetworkBuilder ipNetworkBuilder = new IpNetworkBuilder()
                    .withLocalBindAddress(localBindAddress)
                    .withSubnet("255.255.255.0", 24)
                    .withPort(47808)
                    .withReuseAddress(true);
            String broadcastAddress = bacnetLocalDeviceBean.getBroadcastAddress();
            Integer networkPrefixLength = bacnetLocalDeviceBean.getNetworkPrefixLength();
            if (CharSequenceUtil.isNotBlank(broadcastAddress) && ObjectUtil.isNotEmpty(networkPrefixLength)) {
                ipNetworkBuilder.withBroadcast(broadcastAddress, networkPrefixLength);
            }
            IpNetwork network = ipNetworkBuilder.build();
            DefaultTransport defaultTransport = new DefaultTransport(network);
            int localDeviceId = getLocalDeviceIdMax();
            StaticLog.info("localDeviceId:{}", localDeviceId);
            localDevice = new LocalDevice(localDeviceId, defaultTransport);
            // 添加事件监听器
            MyListener myListener = new MyListener(localDevice, bacnetLocalDeviceBean);
            localDevice.getEventHandler().addListener(myListener);
            // 初始化本地设备
            localDevice.initialize();
            // 注册外部设备（如果有配置）
            registerAsForeignDevice(bacnetLocalDeviceBean, network);
            // 开始远程设备发现
            startRemoteDeviceDiscovery(localDevice,bacnetLocalDeviceBean);
            return localDevice;
        } catch (InterruptedException e) {
            if (ObjectUtil.isNotNull(localDevice)) {
                localDevice.terminate();
            }
            throw new DriverInitException(e);
        }
    }

    /**
     * 注册外部设备
     * @param bacnetLocalDeviceBean
     * @param network
     */
    private static void registerAsForeignDevice(BacnetLocalDeviceBean bacnetLocalDeviceBean, IpNetwork network) {
        String foreignDeviceAddress = bacnetLocalDeviceBean.getForeignDeviceAddress();
        if (StrUtil.isNotBlank(foreignDeviceAddress)) {
            try {
                // 解析地址并注册外部设备
                InetSocketAddress inetSocketAddress = new InetSocketAddress(foreignDeviceAddress, 47808);
                // 注册24小时
                network.registerAsForeignDevice(inetSocketAddress, 3600 * 60 * 24);
                StaticLog.info("Registered foreign device: {}", foreignDeviceAddress);
            } catch (Exception e) {
                StaticLog.error("Failed to register foreign device [address={}]", foreignDeviceAddress, e);
                throw new DriverInitException("Foreign device registration failed", e);
            }
        }
    }

    /**
     * 获取本地设备id
     * @return
     */
    private static int getLocalDeviceIdMax() {
        return BASE_MIN_ID + new Random().nextInt(LOCAL_DEVICE_ID_ADD_MAX);
    }

    private static void startRemoteDeviceDiscovery(LocalDevice localDevice, BacnetLocalDeviceBean bacnetLocalDeviceBean) throws InterruptedException {
        AtomicInteger deviceCounter = new AtomicInteger(0);
        CountDownLatch firstDeviceLatch = new CountDownLatch(1);
        // 注册设备发现回调
        localDevice.startRemoteDeviceDiscovery(device -> {
            int currentCount = deviceCounter.getAndIncrement();
            if (currentCount == 0) {
                firstDeviceLatch.countDown(); // 首个设备发现
            }
        });

        ScheduledExecutorService scheduler = ThreadUtil.createScheduledExecutor(1);
        try {
            // 等待首个设备或超时
            boolean foundFirst = firstDeviceLatch.await(Constant.FIRST_REMOTE_DEVICE_FIND_TIMEOUT_NS, TimeUnit.NANOSECONDS);
            if (!foundFirst) {
                StaticLog.warn("No remote device found within the timeout.");
                return;
            }
            // 持续监控设备新增
            AtomicInteger lastCount = new AtomicInteger(deviceCounter.get());
            scheduler.scheduleAtFixedRate(() -> {
                int current = deviceCounter.get();
                if (current == lastCount.get()) {
                    scheduler.shutdown(); // 超时无新设备，终止监控
                } else {
                    lastCount.set(current);
                }
            }, 0, Constant.REMOTE_DEVICE_FIND_TIMEOUT_MS, TimeUnit.MILLISECONDS);
            // 等待监控结束
            try {
                boolean terminated = scheduler.awaitTermination(DISCOVERY_SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
                if (!terminated) {
                    StaticLog.warn("Device discovery未在{}ms内完成，强制终止", DISCOVERY_SHUTDOWN_TIMEOUT_MS);
                    scheduler.shutdownNow();
                }
            } catch (InterruptedException e) {
                StaticLog.warn("设备发现被中断");
                Thread.currentThread().interrupt(); // 恢复中断状态
            }
        } finally {
            if (!scheduler.isShutdown()) {
                scheduler.shutdownNow();
            }
        }
    }

    public static Boolean connect(BacnetLocalDeviceBean bacnetLocalDeviceBean) throws Exception {
        LocalDevice localDevice = getLocalDevice(bacnetLocalDeviceBean);
        return localDevice.isInitialized();
    }

    public static Boolean isOpen(BacnetLocalDeviceBean bacnetLocalDeviceBean) {
        LocalDevice localDevice = LocalDeviceCacheUtils.get(bacnetLocalDeviceBean);
        return Optional.ofNullable(localDevice).isPresent() && localDevice.isInitialized();
    }

    public static Boolean close(BacnetLocalDeviceBean bacnetLocalDeviceBean) {
        LocalDevice localDevice = LocalDeviceCacheUtils.get(bacnetLocalDeviceBean);
        if (localDevice != null) {
            localDevice.terminate();
        }
        return true;
    }


}


